# ------------------#
# ResBlock+CBAM
# ------------------#
import torch
import torch.nn as nn
import torchvision


debug = False


class ChannelAttentionModule(nn.Module):
    def __init__(self, channel, ratio=16):
        super(ChannelAttentionModule, self).__init__()
        self.avg_pool = nn.AdaptiveAvgPool2d(1)
        self.max_pool = nn.AdaptiveMaxPool2d(1)

        self.shared_MLP = nn.Sequential(
            nn.Conv2d(channel, channel // ratio, 1, bias=False),
            nn.ReLU(),
            nn.Conv2d(channel // ratio, channel, 1, bias=False)
        )
        self.sigmoid = nn.Sigmoid()

    def forward(self, x):
        avgout = self.shared_MLP(self.avg_pool(x))
        if debug: print(' ChannelAttentionModule.avgout.shape:', avgout.shape)
        maxout = self.shared_MLP(self.max_pool(x))
        return self.sigmoid(avgout + maxout)


class SpatialAttentionModule(nn.Module):
    def __init__(self):
        super(SpatialAttentionModule, self).__init__()
        self.conv2d = nn.Conv2d(in_channels=2, out_channels=1, kernel_size=7, stride=1, padding=3)
        self.sigmoid = nn.Sigmoid()

    def forward(self, x):
        avgout = torch.mean(x, dim=1, keepdim=True)
        maxout, _ = torch.max(x, dim=1, keepdim=True)
        out = torch.cat([avgout, maxout], dim=1)
        out = self.sigmoid(self.conv2d(out))
        return out


class CBAM(nn.Module):
    def __init__(self, channel):
        super(CBAM, self).__init__()
        self.channel_attention = ChannelAttentionModule(channel)
        self.spatial_attention = SpatialAttentionModule()

    def forward(self, x):
        print('~~~ CBAM.x.shape:{}'.format(x.shape))
        out = self.channel_attention(x) * x
        if debug: print('~~~ CBAM.outchannels:{}'.format(out.shape))
        out = self.spatial_attention(out) * out
        return out


class ResBlock_CBAM(nn.Module):
    def __init__(self, in_places, places, stride=1, downsampling=False, expansion=4):
        super(ResBlock_CBAM, self).__init__()
        self.expansion = expansion
        self.downsampling = downsampling

        self.bottleneck = nn.Sequential(
            nn.Conv2d(in_channels=in_places, out_channels=places, kernel_size=1, stride=1, bias=False),
            nn.BatchNorm2d(places),
            nn.ReLU(inplace=True),
            nn.Conv2d(in_channels=places, out_channels=places, kernel_size=3, stride=stride, padding=1, bias=False),
            nn.BatchNorm2d(places),
            nn.ReLU(inplace=True),
            nn.Conv2d(in_channels=places, out_channels=places * self.expansion, kernel_size=1, stride=1, bias=False),
            nn.BatchNorm2d(places * self.expansion),
        )
        self.cbam = CBAM(channel=places * self.expansion)

        if self.downsampling:
            self.downsample = nn.Sequential(
                nn.Conv2d(in_channels=in_places, out_channels=places * self.expansion, kernel_size=1, stride=stride,
                          bias=False),
                nn.BatchNorm2d(places * self.expansion)
            )
        self.relu = nn.ReLU(inplace=True)

    def forward(self, x):
        if debug: print('ResBlock_CBAM.x.shape', x.shape)
        residual = x
        out_0 = self.bottleneck(x)
        if debug: print('--- ResBlock_CBAM.bottleneck.out_0.shape', out_0.shape)
        out = self.cbam(out_0)
        if debug: print('--- ResBlock_CBAM.cbam.out.shape', out.shape)
        if self.downsampling:
            residual = self.downsample(x)

        out += residual
        out = self.relu(out)
        return out


def get_total_parameters(model):
    # 遍历模型的每个参数并累加参数数量
    _is_layer = not isinstance(model, torch.nn.modules.container.Sequential)
    if _is_layer:
        layer_params = 0
        layer = model
        for param in layer.parameters():
            layer_params += param.numel()
        # if debug:  print('layer_params:', layer_params)
        return layer_params

    total_params = 0
    with torch.no_grad():
        for layer in model:
            # X = layer(X)
            layer_params = 0
            for param in layer.parameters():
                layer_params += param.numel()
            total_params += layer_params
            # if dbg:
            #     print('layer_params:', layer_params, '---', layer.__class__.__name__, 'output shape:\t', X.shape)
    return total_params


if __name__ == '__main__':
    debug = True
    model = ResBlock_CBAM(in_places=20, places=5, expansion=4)
    print(model, '\n--- model --- total_parameters:', get_total_parameters(model))

    in_x = torch.randn(4, 20, 64, 64)
    out = model(in_x)
    print('--- out.shape:', out.shape)

    # in_x.shape
    # att_1 = model.cbam(in_x)
    # att_1.shape